#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/version.h>

#include "drv_log.h"
#include "securec.h"
#include "hns_mdio.h"

#define STATIC

#define MDIO_DRV_NAME "HiAscend_MDIO"
#define MDIO_BUS_NAME "Hisilicon Ascend MII Bus"

#define MDIO_TIMEOUT 1000000

#define MDIO_LOG_PREFIX "mdio"
#define mdio_err(fmt...) drv_err(MDIO_LOG_PREFIX, fmt)
#define mdio_warn(fmt...) drv_warn(MDIO_LOG_PREFIX, fmt)
#define mdio_info(fmt...) drv_info(MDIO_LOG_PREFIX, fmt)
#define mdio_debug(fmt...) drv_debug(MDIO_LOG_PREFIX, fmt)

#define GPIO_SWPORT_DR 0
#define GPIO_SWPORT_DDR 0x4

#define GPIO_NAME_69 "board-rgmii-gpio-69"
#define GPIO_NAME_71 "board-rgmii-gpio-71"

#define GPIO_TYPE_ASIC 0 /* mini,mdc */
#define GPIO_TYPE_EVB 1
#define GPIO_TYPE_FPGA 2

/* mdio reg */
#define MDIO_COMMAND_REG 0x0
#define MDIO_ADDR_REG 0x4
#define MDIO_WDATA_REG 0x8
#define MDIO_RDATA_REG 0xc
#define MDIO_STA_REG 0x10

/* cfg phy bit map */
#define MDIO_CMD_DEVAD_M 0x1f
#define MDIO_CMD_DEVAD_S 0
#define MDIO_CMD_PRTAD_M 0x1f
#define MDIO_CMD_PRTAD_S 5
#define MDIO_CMD_OP_S 10
#define MDIO_CMD_ST_S 12
#define MDIO_CMD_START_B 14

#define MDIO_ADDR_DATA_M 0xffff
#define MDIO_ADDR_DATA_S 0

#define MDIO_WDATA_DATA_M 0xffff
#define MDIO_WDATA_DATA_S 0

#define MDIO_RDATA_DATA_M 0xffff
#define MDIO_RDATA_DATA_S 0

#define MDIO_STATE_STA_B 0

enum {
    MEM_MDIO_IOBASE,
    MEM_GPIO2_IOBASE,
    MEM_SYSCTRL_IOBASE,
    MEM_IOBASE_MAX,
};

enum mdio_st_clause {
    MDIO_ST_CLAUSE_45 = 0,
    MDIO_ST_CLAUSE_22
};

enum mdio_c22_op_seq {
    MDIO_C22_WRITE = 1,
    MDIO_C22_READ = 2
};

enum mdio_c45_op_seq {
    MDIO_C45_WRITE_ADDR = 0,
    MDIO_C45_WRITE_DATA,
    MDIO_C45_READ_INCREMENT,
    MDIO_C45_READ
};

struct peri_sc_mdio_reg {
    u16 mdio_clk_en;
    u16 mdio_clk_dis;
    u16 mdio_reset_req;
    u16 mdio_reset_dreq;
    u16 mdio_ctrl;
    u16 mdio_clk_st;
    u16 mdio_reset_st;
};

struct hns_mdio_device {
    void *vbase; /* mdio reg base address */
    struct regmap *subctrl_vbase;
    const struct peri_sc_mdio_reg *sc_reg;

    struct platform_device *pdev;
    struct mii_bus *bus;

    struct cdev mdio_cdev;
    struct class *mdio_cdev_cls;
    dev_t mdio_devno;
};

static struct mii_bus *hns_mdiobus = NULL;
struct mii_bus *hns_get_mdiobus(void)
{
    return hns_mdiobus;
}
EXPORT_SYMBOL(hns_get_mdiobus);

static void mdio_write_reg(void *base, u32 reg, u32 value)
{
    u8 __iomem *reg_addr = (u8 __iomem *)base;

    writel_relaxed(value, reg_addr + reg);
}

#define MDIO_WRITE_REG(a, reg, value) mdio_write_reg((a)->vbase, (reg), (value))

static u32 mdio_read_reg(void *base, u32 reg)
{
    u8 __iomem *reg_addr = (u8 __iomem *)base;

    return readl_relaxed(reg_addr + reg);
}

#define mdio_set_field(origin, mask, shift, val) do {                                           \
        (origin) &= (~((mask) << (shift)));        \
        (origin) |= (((val) & (mask)) << (shift)); \
    } while (0)

#define mdio_get_field(origin, mask, shift) (((origin) >> (shift)) & (mask))

static void mdio_set_reg_field(void *base, u32 reg, u32 mask, u32 shift, u32 val)
{
    u32 origin = mdio_read_reg(base, reg);

    mdio_set_field(origin, mask, shift, val);
    mdio_write_reg(base, reg, origin);
}

#define MDIO_SET_REG_FIELD(dev, reg, mask, shift, val) mdio_set_reg_field((dev)->vbase, (reg), (mask), (shift), (val))

STATIC u32 mdio_get_reg_field(void *base, u32 reg, u32 mask, u32 shift)
{
    u32 origin;

    origin = mdio_read_reg(base, reg);
    return mdio_get_field(origin, mask, shift);
}

#define MDIO_GET_REG_FIELD(dev, reg, mask, shift) mdio_get_reg_field((dev)->vbase, (reg), (mask), (shift))

#define MDIO_GET_REG_BIT(dev, reg, bit) mdio_get_reg_field((dev)->vbase, (reg), 0x1ull, (bit))

#define MDIO_CHECK_SET_ST 1
#define MDIO_CHECK_CLR_ST 0

STATIC int hns_mdio_wait_ready(struct mii_bus *bus)
{
    struct hns_mdio_device *mdio_dev = bus->priv;
    u32 cmd_reg_value = 0;
    int i;

    /* waitting for MDIO_COMMAND_REG 's mdio_start==0 */
    /* after that can do read or write */
    for (i = 0; i < MDIO_TIMEOUT; i++) {
        cmd_reg_value = MDIO_GET_REG_BIT(mdio_dev, MDIO_COMMAND_REG, MDIO_CMD_START_B);
        if (!cmd_reg_value)
            break;
    }

    if ((i == MDIO_TIMEOUT) && cmd_reg_value)
        return -ETIMEDOUT;

    return 0;
}

static void hns_mdio_cmd_write(struct hns_mdio_device *mdio_dev, u8 is_c45, u8 op, u8 phy_id, u16 cmd)
{
    u32 cmd_reg_value;
    u8 st = is_c45 ? MDIO_ST_CLAUSE_45 : MDIO_ST_CLAUSE_22;

    cmd_reg_value = st << MDIO_CMD_ST_S;
    cmd_reg_value |= op << MDIO_CMD_OP_S;
    cmd_reg_value |= (phy_id & MDIO_CMD_PRTAD_M) << MDIO_CMD_PRTAD_S;
    cmd_reg_value |= (cmd & MDIO_CMD_DEVAD_M) << MDIO_CMD_DEVAD_S;
    cmd_reg_value |= 1 << MDIO_CMD_START_B;

    MDIO_WRITE_REG(mdio_dev, MDIO_COMMAND_REG, cmd_reg_value);
}

/* *
 * hns_mdio_write - access phy register
 * @bus: mdio bus
 * @phy_id: phy id
 * @regnum: register num
 * @value: register value
 *
 * Return 0 on success, negative on failure
 */
STATIC int hns_mdio_write(struct mii_bus *bus, int phy_id, int regnum, u16 data)
{
    int ret;
    struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;
    u8 devad = (((u32)regnum >> 16) & 0x1f);
    u8 is_c45 = !!((u32)regnum & MII_ADDR_C45);
    u16 reg = (u16)((u32)regnum & 0xffff);
    u8 op;
    u16 cmd_reg_cfg;

    /* wait for ready */
    ret = hns_mdio_wait_ready(bus);
    if (ret) {
        mdio_err("MDIO bus is busy\n");
        return ret;
    }

    if (!is_c45) {
        cmd_reg_cfg = reg;
        op = MDIO_C22_WRITE;
    } else {
        /* config the cmd-reg to write addr */
        MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M, MDIO_ADDR_DATA_S, reg);

        hns_mdio_cmd_write(mdio_dev, is_c45, MDIO_C45_WRITE_ADDR, phy_id, devad);

        /* check for read or write opt is finished */
        ret = hns_mdio_wait_ready(bus);
        if (ret) {
            mdio_err("MDIO bus is busy\n");
            return ret;
        }

        /* config the data needed writing */
        cmd_reg_cfg = devad;
        op = MDIO_C45_WRITE_ADDR;
    }

    MDIO_SET_REG_FIELD(mdio_dev, MDIO_WDATA_REG, MDIO_WDATA_DATA_M, MDIO_WDATA_DATA_S, data);

    hns_mdio_cmd_write(mdio_dev, is_c45, op, phy_id, cmd_reg_cfg);

    return 0;
}

/* *
 * hns_mdio_read - access phy register
 * @bus: mdio bus
 * @phy_id: phy id
 * @regnum: register num
 * @value: register value
 *
 * Return phy register value
 */
STATIC int hns_mdio_read(struct mii_bus *bus, int phy_id, int regnum)
{
    int ret;
    u16 reg_val;
    u8 devad = (((u32)regnum >> 16) & 0x1f);
    u8 is_c45 = !!((u32)regnum & MII_ADDR_C45);
    u16 reg = (u16)((u32)regnum & 0xffff);
    struct hns_mdio_device *mdio_dev = (struct hns_mdio_device *)bus->priv;

    /* Step 1: wait for ready */
    ret = hns_mdio_wait_ready(bus);
    if (ret) {
        mdio_err("MDIO bus is busy\n");
        return ret;
    }

    if (!is_c45) {
        hns_mdio_cmd_write(mdio_dev, is_c45, MDIO_C22_READ, phy_id, reg);
    } else {
        MDIO_SET_REG_FIELD(mdio_dev, MDIO_ADDR_REG, MDIO_ADDR_DATA_M, MDIO_ADDR_DATA_S, reg);

        /* Step 2; config the cmd-reg to write addr */
        hns_mdio_cmd_write(mdio_dev, is_c45, MDIO_C45_WRITE_ADDR, phy_id, devad);

        /* Step 3: check for read or write opt is finished */
        ret = hns_mdio_wait_ready(bus);
        if (ret) {
            mdio_err("MDIO bus is busy\n");
            return ret;
        }

        hns_mdio_cmd_write(mdio_dev, is_c45, MDIO_C45_WRITE_ADDR, phy_id, devad);
    }

    /* Step 5: waitting for MDIO_COMMAND_REG 's mdio_start==0, */
    /* check for read or write opt is finished */
    ret = hns_mdio_wait_ready(bus);
    if (ret) {
        mdio_err("MDIO bus is busy\n");
        return ret;
    }

    reg_val = MDIO_GET_REG_BIT(mdio_dev, MDIO_STA_REG, MDIO_STATE_STA_B);
    if (reg_val) {
        mdio_err("ERROR! MDIO Read failed!\n");
        return -EBUSY;
    }

    /* Step 6; get out data */
    reg_val = (u16)MDIO_GET_REG_FIELD(mdio_dev, MDIO_RDATA_REG, MDIO_RDATA_DATA_M, MDIO_RDATA_DATA_S);
    return reg_val;
}

/* *
 * hns_mdio_bus_name - get mdio bus name
 * @name: mdio bus name
 * @np: mdio device node pointer
 */
static void hns_mdio_bus_name(char *name, struct device_node *np)
{
    const u32 *addr = NULL;
    u64 taddr = OF_BAD_ADDR;
    int ret;

    addr = of_get_address(np, 0, NULL, NULL);
    if (NULL != addr)
        taddr = of_translate_address(np, addr);

    ret = snprintf_s(name, MII_BUS_ID_SIZE, MII_BUS_ID_SIZE - 1, "%s@%llx", np->name, (unsigned long long)taddr);
    if (ret > 0) {
        mdio_info("get bus name suceess\n");
    }
}

void hns_gpio_set(struct device_node *np, const char *propname, int out_in, int value)
{
    unsigned int gpio;

    gpio = of_get_named_gpio(np, propname, 0);
    if (!gpio_is_valid(gpio)) {
        mdio_err("gpio %d(%s) is invalid\n", gpio, propname);
        return;
    }

    if (gpio_request(gpio, propname)) {
        mdio_err("request gpio %d(%s) failed\n", gpio, propname);
        return;
    }

    (void)gpio_direction_output(gpio, out_in);
    msleep(20);

    gpio_set_value_cansleep(gpio, value);
    msleep(20);

    gpio_free(gpio);

    mdio_info("gpio %d(%s) set success\n", gpio, propname);
}

/*
 * GPIO069/GPIO071 usefull:
 * 1. on evb, both used for rgmii as a unreset pin, should pull up.
 * 2. mini(EP/RC) or mdc, GPIO069 used for irq, GPIO071 used for LSW unreset.
 * GPIO069 nic need not set, GPIO071 should pull up.
 */
void hns_gpio_unreset(struct device_node *node, int set_gpio69)
{
    if (set_gpio69) {
        hns_gpio_set(node, GPIO_NAME_69, 1, 1);
    }

    hns_gpio_set(node, GPIO_NAME_71, 1, 1);

    mdio_info("rgmii gpio unreset finish.\n");
}

void hns_asic_init(struct device_node *np, struct mii_bus *new_bus)
{
    int set_gpio69 = 0;
    u32 gpio_type;

    if (of_property_read_u32_index(np, "gpio-type", 0, &gpio_type)) {
        mdio_warn("gpio-type node is not setted.\n");
        return;
    }

    mdio_info("gpio_type = %d\n", gpio_type);

    if ((gpio_type != GPIO_TYPE_ASIC) && (gpio_type != GPIO_TYPE_EVB))
        return;

    if (gpio_type == GPIO_TYPE_EVB)
        set_gpio69 = 1;

    hns_gpio_unreset(np, set_gpio69);
    msleep(10);

    mdio_info("hi mini hw init success\n");
}

STATIC int mdio_open(struct inode *node, struct file *file)
{
    struct hns_mdio_device *mdio_dev;

    mdio_dev = container_of(node->i_cdev, struct hns_mdio_device, mdio_cdev);
    file->private_data = (void *)mdio_dev;

    return 0;
}

STATIC int mdio_release(struct inode *node, struct file *file)
{
    file->private_data = NULL;
    return 0;
}

STATIC int mdio_ioctl_cmd(struct hns_mdio_device *mdio_dev, unsigned int cmd, struct hns_mdio_arg *mdio_arg)
{
    int ret = 0;

    switch (cmd) {
        case MDIO_IO_READ:
            ret = mdiobus_read(mdio_dev->bus, mdio_arg->devaddr, mdio_arg->regaddr);
            if (ret < 0) {
                return ret;
            }

            mdio_arg->value_out = (u16)ret;
            break;
        case MDIO_IO_WRITE:
            ret = mdiobus_write(mdio_dev->bus, mdio_arg->devaddr, mdio_arg->regaddr, mdio_arg->value_in);
            if (ret < 0) {
                return ret;
            }
            break;

        default:
            mdio_err("command(%d) not support!\n", cmd);
            ret = -EINVAL;
            break;
    }

    return ret;
}

STATIC long mdio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    void __user *argp = (void __user *)((uintptr_t)arg);
    struct hns_mdio_device *mdio_dev = NULL;
    struct hns_mdio_arg mdio_arg;
    int ret;

    if (argp == NULL) {
        mdio_err("write back address is null\n");
        return -1;
    }

    if (copy_from_user((void *)&mdio_arg, argp, sizeof(struct hns_mdio_arg))) {
        mdio_err("copy_from_user fail!\n");
        return -1;
    }

    mdio_dev = (struct hns_mdio_device *)file->private_data;

    ret = mdio_ioctl_cmd(mdio_dev, cmd, &mdio_arg);
    if (ret < 0) {
        mdio_err("ioctl command failed, ret = %d\n", ret);
        return -1;
    }

    if (copy_to_user(argp, (void *)&mdio_arg, sizeof(struct hns_mdio_arg))) {
        mdio_err("copy_to_user fail!\n");
        return -1;
    }
    return 0;
}

static const struct file_operations mdio_ops = {
    .owner = THIS_MODULE,
    .open = mdio_open,
    .release = mdio_release,
    .unlocked_ioctl = mdio_ioctl,
};

STATIC int hns_mdio_cdev_create(struct hns_mdio_device *mdio_dev)
{
    struct device *drv_dev = NULL;
    int ret;

    ret = alloc_chrdev_region(&mdio_dev->mdio_devno, 0, MDIO_CDEV_MINORS, MDIO_CDEV_NAME);
    if (ret) {
        mdio_err("alloc_chrdev_region failed, ret = %d\n", ret);
        goto _out;
    }

    mdio_dev->mdio_cdev_cls = class_create(THIS_MODULE, MDIO_CDEV_NAME);
    if (IS_ERR(mdio_dev->mdio_cdev_cls)) {
        mdio_err("class_create faild, ret = %pK\n", mdio_dev->mdio_cdev_cls);
        goto _unregister_region;
    }

    drv_dev = device_create(mdio_dev->mdio_cdev_cls, NULL, mdio_dev->mdio_devno, NULL, MDIO_CDEV_NAME);
    if (IS_ERR(drv_dev)) {
        mdio_err("failed to create device, ret = %ld\n", PTR_ERR(drv_dev));
        goto _class_del;
    }

    cdev_init(&mdio_dev->mdio_cdev, &mdio_ops);
    mdio_dev->mdio_cdev.owner = THIS_MODULE;
    ret = cdev_add(&mdio_dev->mdio_cdev, mdio_dev->mdio_devno, MDIO_CDEV_MINORS);
    if (ret) {
        mdio_err("cdev_add failed, ret = %d\n", ret);
        goto _device_del;
    }

    mdio_info("mdio cdev create finish, name is %s\n", MDIO_CDEV_NAME);
    return 0;

_device_del:
    device_destroy(mdio_dev->mdio_cdev_cls, mdio_dev->mdio_devno);
_class_del:
    class_destroy(mdio_dev->mdio_cdev_cls);
_unregister_region:
    unregister_chrdev_region(mdio_dev->mdio_devno, MDIO_CDEV_MINORS);
_out:
    return -1;
}

int hns_mdio_cdev_del(struct hns_mdio_device *mdio_dev)
{
    cdev_del(&mdio_dev->mdio_cdev);
    device_destroy(mdio_dev->mdio_cdev_cls, mdio_dev->mdio_devno);
    class_destroy(mdio_dev->mdio_cdev_cls);
    unregister_chrdev_region(mdio_dev->mdio_devno, MDIO_CDEV_MINORS);
    mdio_dev->mdio_cdev_cls = NULL;

    mdio_info("mdio cdev del finish\n");
    return 0;
}

/* hi1382 peri sc mdio reg */
static const struct peri_sc_mdio_reg hi1382_peri_sc_mdio_reg = {
    .mdio_clk_en = 0x550,
    .mdio_clk_dis = 0x554,
    .mdio_reset_req = 0xc40,
    .mdio_reset_dreq = 0xc44,
    .mdio_ctrl = 0x2010,
    .mdio_clk_st = 0x5550,
    .mdio_reset_st = 0x5c40,
};

/* hip05 use the same peri sc mdio regs */
static const struct peri_sc_mdio_reg hip05_peri_sc_mdio_reg = {
    .mdio_clk_en = 0x338,
    .mdio_clk_dis = 0x33c,
    .mdio_reset_req = 0xa38,
    .mdio_reset_dreq = 0xa3c,
    .mdio_ctrl = 0x2010,
    .mdio_clk_st = 0x531c,
    .mdio_reset_st = 0x5a1c,
};

/* hip05/hi1610 use the same peri sc mdio regs */
static const struct peri_sc_mdio_reg hip06_peri_sc_mdio_reg[] = {
    {
        .mdio_clk_en = 0x338,
        .mdio_clk_dis = 0x33c,
        .mdio_reset_req = 0xa38,
        .mdio_reset_dreq = 0xa3c,
        .mdio_ctrl = 0x2010,
        .mdio_clk_st = 0x531c,
        .mdio_reset_st = 0x5a1c,
    },
    {
        .mdio_clk_en = 0x338,
        .mdio_clk_dis = 0x33c,
        .mdio_reset_req = 0xa40,
        .mdio_reset_dreq = 0xa44,
        .mdio_ctrl = 0x2010,
        .mdio_clk_st = 0x531c,
        .mdio_reset_st = 0x5a20,
    }
};

static const struct peri_sc_mdio_reg hi1616_peri_sc_mdio_reg[] = {
    {
        .mdio_clk_en = 0x550,
        .mdio_clk_dis = 0x554,
        .mdio_reset_req = 0xc40,
        .mdio_reset_dreq = 0xc44,
        .mdio_ctrl = 0x2010,
        .mdio_clk_st = 0x5550,
        .mdio_reset_st = 0x5c40,
    },
    {
        .mdio_clk_en = 0x338,
        .mdio_clk_dis = 0x33c,
        .mdio_reset_req = 0xa40,
        .mdio_reset_dreq = 0xa44,
        .mdio_ctrl = 0x2010,
        .mdio_clk_st = 0x531c,
        .mdio_reset_st = 0x5a20,
    }
};

static const struct peri_sc_mdio_reg hi1910_peri_sc_mdio_reg = {
    .mdio_clk_en = 0x630,
    .mdio_clk_dis = 0x634,
    .mdio_reset_req = 0xd30,
    .mdio_reset_dreq = 0xd34,
    .mdio_ctrl = 0x2038,
    .mdio_clk_st = 0x5630,
    .mdio_reset_st = 0x5d30,
};


static const struct of_device_id hns_mdio_match[] = {
    {
        .compatible = "hisilicon,mdio",
        .data = &hip05_peri_sc_mdio_reg
    },
    {
        .compatible = "hisilicon,hip04-mdio",
    },
    {
        .compatible = "hisilicon,hns-mdio",
        .data = hip06_peri_sc_mdio_reg
    },
    {
        .compatible = "hisilicon,hi1382-mdio",
        .data = &hi1382_peri_sc_mdio_reg
    },
    {
        .compatible = "hisilicon,hi1616-mdio",
        .data = hi1616_peri_sc_mdio_reg
    },
    {
        .compatible = "hisilicon,hi1910-mdio",
        .data = &hi1910_peri_sc_mdio_reg
    },
    { }
};

MODULE_DEVICE_TABLE(of, hns_mdio_match);


static int hns_mdio_scan_phy(struct device_node *np)
{
    bool scanphy = false;
    struct device_node *child = NULL;

    /* when dts mdio no phy node */
    for_each_available_child_of_node(np, child)
    {
        u32 addr = 0;
        int ret;
        ret = of_property_read_u32(child, "reg", &addr);
        if ((ret == 0) && (addr < PHY_MAX_ADDR)) {
            scanphy = true;
        }
    }
    return scanphy;
}

static int hns_mdiobus_register(struct device_node *np, struct mii_bus *new_bus, int *no_phy)
{
    bool scanphy;
    int ret;

    scanphy = hns_mdio_scan_phy(np);
    if (scanphy == false) {
        /* support phy addr is 0 or 1 */
        new_bus->phy_mask = ~0 << PHY_ADDR_FIXED;
        mdio_info("phy_mask: %u\n", new_bus->phy_mask);
        ret = mdiobus_register(new_bus);
        if (ret) {
            /* when with lan switch, ignore err */
            mdio_info("Cannot register phy!\n");
            *no_phy = 1;
        }
        /* without phy always return success */
        ret = 0;
    } else {
        ret = of_mdiobus_register(new_bus, np);
        if (ret) {
            mdio_err("Cannot register as MDIO bus!\n");
            return ret;
        }
    }
    return ret;
}

/* *
 * hns_mdio_probe - probe mdio device
 * @pdev: mdio platform device
 *
 * Return 0 on success, negative on failure
 */
STATIC int hns_mdio_probe(struct platform_device *pdev)
{
    struct device_node *np = NULL, *syscon_np = NULL;
    struct hns_mdio_device *mdio_dev = NULL;
    struct mii_bus *new_bus = NULL;
    struct resource *res = NULL;
    const struct of_device_id *of_id = NULL;
    int no_phy = 0;
    int ret;

    if (NULL == pdev) {
        mdio_err("pdev is NULL!\n");
        return -ENODEV;
    }

    np = pdev->dev.of_node;
    mdio_dev = (struct hns_mdio_device *)devm_kzalloc(&pdev->dev, sizeof(struct hns_mdio_device), GFP_KERNEL);
    if (NULL == mdio_dev)
        return -ENOMEM;

    new_bus = devm_mdiobus_alloc(&pdev->dev);
    if (NULL == new_bus) {
        mdio_err("mdiobus_alloc fail!\n");
        return -ENOMEM;
    }

    of_id = of_match_device(hns_mdio_match, &pdev->dev);
    if (NULL == of_id)
        return -ENODEV;
    mdio_dev->sc_reg = of_id->data;

    new_bus->name = MDIO_BUS_NAME;
    new_bus->read = hns_mdio_read;
    new_bus->write = hns_mdio_write;
    new_bus->priv = mdio_dev;
    hns_mdio_bus_name(new_bus->id, np);

    res = platform_get_resource(pdev, IORESOURCE_MEM, MEM_MDIO_IOBASE);
    mdio_dev->vbase = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(mdio_dev->vbase)) {
        ret = PTR_ERR(mdio_dev->vbase);
        return ret;
    }

    syscon_np = of_parse_phandle(np, "subctrl-vbase", 0);
    if (NULL != syscon_np) {
        mdio_dev->subctrl_vbase = syscon_node_to_regmap(syscon_np);
        if (IS_ERR(mdio_dev->subctrl_vbase)) {
            mdio_warn("no syscon hisilicon\n");
            mdio_dev->subctrl_vbase = NULL;
        } else {
            if (!memcmp(syscon_np->name, "wrap_ctrl", strlen("wrap_ctrl")))
                mdio_dev->sc_reg++;
        }
    } else {
        mdio_warn("no subctrl_vbase\n");
        mdio_dev->subctrl_vbase = NULL;
    }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
    /* cloud
     * In 4.11 kernel, the array of interrupts is defined :
     * int irq[PHY_MAX_ADDR];
     * so, in cloud scene no need to alloc memory for irq
     */
#else
    /* mini */
    new_bus->irq = devm_kcalloc(&pdev->dev, PHY_MAX_ADDR, sizeof(int), GFP_KERNEL);
    if (!new_bus->irq)
        return -ENOMEM;
#endif

    new_bus->parent = &pdev->dev;
    platform_set_drvdata(pdev, new_bus);
    hns_mdiobus = new_bus;

    hns_asic_init(np, new_bus);

    ret = hns_mdiobus_register(np, new_bus, &no_phy);
    if (ret) {
        platform_set_drvdata(pdev, NULL);
        return ret;
    }

    ret = hns_mdio_cdev_create(mdio_dev);
    if (ret) {
        mdio_err("hns_mdio_cdev_create failed\n");
        if (!no_phy) {
            mdiobus_unregister(new_bus);
        }
        platform_set_drvdata(pdev, NULL);
    }
    mdio_dev->pdev = pdev;
    mdio_dev->bus = new_bus;
    return 0;
}

/* *
 * hns_mdio_remove - remove mdio device
 * @pdev: mdio platform device
 *
 * Return 0 on success, negative on failure
 */
STATIC int hns_mdio_remove(struct platform_device *pdev)
{
    struct hns_mdio_device *mdio_dev = NULL;
    struct mii_bus *bus = NULL;

    bus = platform_get_drvdata(pdev);
    mdio_dev = (struct hns_mdio_device *)bus->priv;

    mdiobus_unregister(bus);
    platform_set_drvdata(pdev, NULL);

    hns_mdio_cdev_del(mdio_dev);

    mdio_info("mdio remove finish\n");
    return 0;
}

static struct platform_driver hns_mdio_driver = {
    .probe = hns_mdio_probe,
    .remove = hns_mdio_remove,
    .driver = {
        .name = MDIO_DRV_NAME,
        .of_match_table = hns_mdio_match,
    },
};

module_platform_driver(hns_mdio_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
MODULE_DESCRIPTION("Hisilicon HNS MDIO driver");
MODULE_ALIAS("platform:" MDIO_DRV_NAME);
